// SPDX-License-Identifier: GPL-2.0-or-later

#define LOG_TAG         "Tool"

#include "cts_config.h"
#include "cts_platform.h"
#include "cts_core.h"
#include "cts_firmware.h"
#include "cts_test.h"
#include "cts_tcs.h"

#ifdef CONFIG_CTS_LEGACY_TOOL

#define CTS_TOOL_IOCTL_TCS_RW_BUF_MAX_SIZ     1024
#define CTS_TOOL_IOCTL_TCS_RW_OPFLAG_READ     1
#define CTS_TOOL_IOCTL_TCS_RW_OPFLAG_WRITE    2

#pragma pack(1)
/** Tool command structure */
struct cts_tool_cmd {
    u8 cmd;
    u8 flag;
    u8 circle;
    u8 times;
    u8 retry;
    u32 data_len;
    u8 addr_len;
    u8 addr[4];
    u8 tcs[2];
    u8 data[PAGE_SIZE];
};
struct cts_ioctl_tcs_rw_data {
    u32 opflags;
    u32 txlen;
    u32 rxlen;
    u8 __user *txbuf;
    u8 __user *rxbuf;
};
#pragma pack()

#define CTS_TOOL_CMD_HEADER_LENGTH            (16)

enum cts_tool_cmd_code {
    CTS_TOOL_CMD_GET_PANEL_PARAM = 0,
    CTS_TOOL_CMD_GET_DOWNLOAD_STATUS = 2,
    CTS_TOOL_CMD_GET_RAW_DATA = 4,
    CTS_TOOL_CMD_GET_DIFF_DATA = 6,
    CTS_TOOL_CMD_READ_HOSTCOMM = 12,
    CTS_TOOL_CMD_READ_ADC_STATUS = 14,
    CTS_TOOL_CMD_READ_GESTURE_INFO = 16,
    CTS_TOOL_CMD_READ_HOSTCOMM_MULTIBYTE = 18,
    CTS_TOOL_CMD_READ_PROGRAM_MODE_MULTIBYTE = 20,
    CTS_TOOL_CMD_READ_ICTYPE = 22,
    CTS_TOOL_CMD_I2C_DIRECT_READ = 24,
    CTS_TOOL_CMD_GET_DRIVER_INFO = 26,
    CTS_TOOL_CMD_TCS_READ_HW_CMD = 28,
    CTS_TOOL_CMD_TCS_READ_DDI_CMD = 30,
    CTS_TOOL_CMD_TCS_READ_FW_CMD = 32,
    CTS_TOOL_CMD_GET_BASE_DATA = 34,
    CTS_TOOL_CMD_GET_INT_DATAS = 36,

    CTS_TOOL_CMD_UPDATE_PANEL_PARAM_IN_SRAM = 1,
    CTS_TOOL_CMD_DOWNLOAD_FIRMWARE_WITH_FILENAME = 3,
    CTS_TOOL_CMD_DOWNLOAD_FIRMWARE = 5,
    CTS_TOOL_CMD_WRITE_HOSTCOMM = 11,
    CTS_TOOL_CMD_WRITE_HOSTCOMM_MULTIBYTE = 15,
    CTS_TOOL_CMD_WRITE_PROGRAM_MODE_MULTIBYTE = 17,
    CTS_TOOL_CMD_I2C_DIRECT_WRITE = 19,
    CTS_TOOL_CMD_TCS_WRITE_HW_CMD = 21,
    CTS_TOOL_CMD_TCS_WRITE_DDI_CMD = 23,
    CTS_TOOL_CMD_TCS_WRITE_FW_CMD = 25,
    CTS_TOOL_CMD_TCS_ENABLE_GET_RAWDATA_CMD = 27,
    CTS_TOOL_CMD_TCS_DISABLE_GET_RAWDATA_CMD = 29,
    CTS_TOOL_CMD_SET_INT_DATA_TYPE_AND_METHOD = 31,

};

struct cts_test_ioctl_data {
    __u32 ntests;
    struct cts_test_param __user *tests;
};


#define CTS_TOOL_IOCTL_GET_DRIVER_VERSION   _IOR('C', 0x00, u32 *)
#define CTS_TOOL_IOCTL_GET_DEVICE_TYPE      _IOR('C', 0x01, u32 *)
#define CTS_TOOL_IOCTL_GET_FW_VERSION       _IOR('C', 0x02, u16 *)
#define CTS_TOOL_IOCTL_GET_RESOLUTION       _IOR('C', 0x03, u32 *) /* X in LSW, Y in MSW */
#define CTS_TOOL_IOCTL_GET_ROW_COL          _IOR('C', 0x04, u16 *) /* row in LSW, col in MSW */
#define CTS_TOOL_IOCTL_GET_MODULE_ID        _IOWR('C', 0x05, u32 *)

#define CTS_TOOL_IOCTL_TEST                 _IOWR('C', 0x10, struct cts_test_ioctl_data *)
#define CTS_TOOL_IOCTL_RDWR_REG             _IOWR('C', 0x20, struct cts_rdwr_reg_ioctl_data *)
#define CTS_TOOL_IOCTL_UPGRADE_FW           _IOWR('C', 0x21, struct cts_upgrade_fw_ioctl_data *)

#define CTS_TOOL_IOCTL_TCS_RW               _IOWR('C', 0x30, struct cts_ioctl_tcs_rw_data *)

#define CTS_DRIVER_VERSION_CODE ((CFG_CTS_DRIVER_MAJOR_VERSION << 16) | \
        (CFG_CTS_DRIVER_MINOR_VERSION << 8) | \
        (CFG_CTS_DRIVER_PATCH_VERSION << 0))

static struct cts_tool_cmd cts_tool_cmd;
static char cts_tool_firmware_filepath[PATH_MAX];
/* If CFG_CTS_MAX_I2C_XFER_SIZE < 58(PC tool length), this is neccessary */
#ifdef CONFIG_CTS_I2C_HOST
static u32 cts_tool_direct_access_addr = 0;
#endif /* CONFIG_CTS_I2C_HOST */

static int cts_tool_open(struct inode *inode, struct file *file)
{
    file->private_data = pde_data(inode);
    return 0;
}

static ssize_t cts_tool_read(struct file *file,
        char __user *buffer, size_t count, loff_t *ppos)
{
#define RAWDATA_BUFFER_SIZE(cts_dev) \
    (cts_dev->hwdata->num_row * cts_dev->hwdata->num_col * 2)
    struct chipone_ts_data *cts_data;
    struct cts_tool_cmd *cmd;
    struct cts_device *cts_dev;
    int ret = 0;

    cts_data = (struct chipone_ts_data *)file->private_data;
    if (cts_data == NULL) {
        cts_err("Read with private_data = NULL");
        return -EIO;
    }

    cmd = &cts_tool_cmd;
    cts_dev = &cts_data->cts_dev;
    cts_lock_device(cts_dev);

    cts_dbg("read: flag:%d, addr:0x%06x, tcm:0x%04x, datalen:%d",
            cmd->cmd, get_unaligned_le32(cmd->addr),
            get_unaligned_le16(cmd->tcs), cmd->data_len);

    switch (cmd->cmd) {
    case CTS_TOOL_CMD_TCS_READ_HW_CMD:
        cts_tcs_read_hw_reg(cts_dev, get_unaligned_le32(cmd->addr),
                cmd->data, cmd->data_len);
        break;
    case CTS_TOOL_CMD_TCS_READ_DDI_CMD:
        cts_tcs_read_ddi_reg(cts_dev, get_unaligned_le32(cmd->addr),
                cmd->data, cmd->data_len);
        break;
    case CTS_TOOL_CMD_TCS_READ_FW_CMD:
        cts_tcs_read_reg(cts_dev, get_unaligned_le16(cmd->tcs),
                cmd->data, cmd->data_len);
        break;
    case CTS_TOOL_CMD_GET_PANEL_PARAM:
        cts_info("Get panel param len: %u", cmd->data_len);
        break;
    case CTS_TOOL_CMD_GET_DOWNLOAD_STATUS:
        cmd->data[0] = 100;
        cts_info("Get update status = %hhu", cmd->data[0]);
        break;

    case CTS_TOOL_CMD_GET_RAW_DATA:
    case CTS_TOOL_CMD_GET_DIFF_DATA:
    case CTS_TOOL_CMD_GET_BASE_DATA:
        cts_err("Get %s data row: %u col: %u len: %u, source: %x",
             cmd->cmd == CTS_TOOL_CMD_GET_RAW_DATA ? "raw" : "diff",
             cmd->addr[1], cmd->addr[0], cmd->data_len, (cmd->data[1] & 0xf0));

        if (cmd->cmd == CTS_TOOL_CMD_GET_RAW_DATA) {
            ret = cts_tcs_tool_get_rawdata(cts_dev, (u8 *) cmd->data, (cmd->data[1] & 0xf0) << 8);
        } else if (cmd->cmd == CTS_TOOL_CMD_GET_DIFF_DATA) {
            ret = cts_tcs_tool_get_manual_diff(cts_dev, (u8 *) cmd->data, (cmd->data[1] & 0xf0) << 8);
        } else if (cmd->cmd == CTS_TOOL_CMD_GET_BASE_DATA) {
            ret = cts_tcs_tool_get_basedata(cts_dev, (u8 *) cmd->data, (cmd->data[1] & 0xf0) << 8);
        }
        if (ret) {
            cts_err("Get %s data failed %d",
                cmd->cmd == CTS_TOOL_CMD_GET_RAW_DATA ? "raw" :
                cmd->cmd ==
                CTS_TOOL_CMD_GET_DIFF_DATA ? "diff" : "base",
                ret);
        }
        break;
    case CTS_TOOL_CMD_GET_INT_DATAS:
        memcpy((u8 *)cmd->data, cts_dev->int_data, cts_dev->fwdata.int_data_size);
        cmd->data_len = cts_dev->fwdata.int_data_size;
        break;
    case CTS_TOOL_CMD_READ_HOSTCOMM:
        ret = cts_fw_reg_readb(cts_dev, get_unaligned_le16(cmd->addr), cmd->data);
        if (ret) {
            cts_err("Read firmware reg addr 0x%04x failed %d",
                get_unaligned_le16(cmd->addr), ret);
        } else {
            cts_dbg("Read firmware reg addr 0x%04x, val=0x%02x",
                get_unaligned_le16(cmd->addr), cmd->data[0]);
        }
        break;

#ifdef CFG_CTS_GESTURE
    case CTS_TOOL_CMD_READ_GESTURE_INFO:
        ret = cts_get_gesture_info(cts_dev, cmd->data);
        if (ret)
            cts_err("Get gesture info failed %d", ret);
        break;
#endif /* CFG_CTS_GESTURE */

    case CTS_TOOL_CMD_READ_HOSTCOMM_MULTIBYTE:
        cmd->data_len = min((size_t)cmd->data_len, sizeof(cmd->data));
        ret = cts_fw_reg_readsb(cts_dev, get_unaligned_le16(cmd->addr),
                cmd->data, cmd->data_len);
        if (ret) {
            cts_err("Read firmware reg addr 0x%04x len %u failed %d",
                    get_unaligned_le16(cmd->addr), cmd->data_len, ret);
        } else {
            cts_dbg("Read firmware reg addr 0x%04x len %u",
                get_unaligned_le16(cmd->addr), cmd->data_len);
        }
        break;

    case CTS_TOOL_CMD_READ_PROGRAM_MODE_MULTIBYTE:
        cts_dbg("Read under program mode addr 0x%06x len %u",
            (cmd->flag << 16) | get_unaligned_le16(cmd->addr),
            cmd->data_len);
        ret = cts_enter_program_mode(cts_dev);
        if (ret) {
            cts_err("Enter program mode failed %d", ret);
            break;
        }

        ret = cts_sram_readsb(&cts_data->cts_dev,
                get_unaligned_le16(cmd->addr), cmd->data, cmd->data_len);
        if (ret) {
            cts_err("Read under program mode I2C xfer failed %d",
                ret);
        }

        ret = cts_enter_normal_mode(cts_dev);
        if (ret) {
            cts_err("Enter normal mode failed %d", ret);
            break;
        }
        break;

    case CTS_TOOL_CMD_READ_ICTYPE:
        cts_info("Get IC type");
        break;

#ifdef CONFIG_CTS_I2C_HOST
    case CTS_TOOL_CMD_I2C_DIRECT_READ:
        {
            u32 addr_width;
            char *wr_buff = NULL;
            u8 addr_buff[4];
            size_t left_size, max_xfer_size;
            u8 *data;

            if (cmd->addr[0] != CTS_DEV_PROGRAM_MODE_I2CADDR) {
                cmd->addr[0] = CTS_DEV_NORMAL_MODE_I2CADDR;
                addr_width = 2;
            } else {
                addr_width =
                    cts_dev->hwdata->program_addr_width;
            }

            cts_dbg("Direct read from i2c_addr 0x%02x addr 0x%06x size %u",
                    cmd->addr[0], cts_tool_direct_access_addr, cmd->data_len);

            left_size = cmd->data_len;
            max_xfer_size = cts_plat_get_max_i2c_xfer_size(cts_dev->pdata);
            data = cmd->data;
            while (left_size) {
                size_t xfer_size = min(left_size, max_xfer_size);
                ret = cts_plat_i2c_read(cts_data->pdata, cmd->addr[0], wr_buff,
                        addr_width, data, xfer_size, 1, 0);
                if (ret) {
                    cts_err("Direct read i2c_addr 0x%02x addr 0x%06x "
                            "len %zu failed %d", cmd->addr[0],
                            cts_tool_direct_access_addr,
                            xfer_size, ret);
                    break;
                }

                left_size -= xfer_size;
                if (left_size) {
                    data += xfer_size;
                    cts_tool_direct_access_addr += xfer_size;
                    if (addr_width == 2) {
                        put_unaligned_be16(cts_tool_direct_access_addr, addr_buff);
                    } else if (addr_width == 3) {
                        put_unaligned_be24(cts_tool_direct_access_addr, addr_buff);
                    }
                    wr_buff = addr_buff;
                }
            }
        }
        break;
#endif
    case CTS_TOOL_CMD_GET_DRIVER_INFO:
        break;

    default:
        cts_err("Read unknown command %u", cmd->cmd);
        ret = -EINVAL;
        break;
    }

    cts_unlock_device(cts_dev);

    if (ret == 0) {
        if (cmd->cmd == CTS_TOOL_CMD_I2C_DIRECT_READ) {
            ret = copy_to_user(buffer + CTS_TOOL_CMD_HEADER_LENGTH,
                    cmd->data, cmd->data_len);
        } else {
            ret = copy_to_user(buffer, cmd->data, cmd->data_len);
        }
        if (ret) {
            cts_err("Copy data to user buffer failed %d", ret);
            return 0;
        }

        return cmd->data_len;
    }

    return 0;
}

static ssize_t cts_tool_write(struct file *file,
        const char __user *buffer, size_t count, loff_t *ppos)
{
    struct chipone_ts_data *cts_data;
    struct cts_device *cts_dev;
    struct cts_tool_cmd *cmd;
    int ret = 0;

    if (count < CTS_TOOL_CMD_HEADER_LENGTH || count > PAGE_SIZE) {
        cts_err("Write len %zu invalid", count);
        return -EFAULT;
    }

    cts_data = (struct chipone_ts_data *)file->private_data;
    if (cts_data == NULL) {
        cts_err("Write with private_data = NULL");
        return -EIO;
    }

    cmd = &cts_tool_cmd;
    ret = copy_from_user(cmd, buffer, CTS_TOOL_CMD_HEADER_LENGTH);
    cts_info("write: flag:%d, addr:0x%06x, tcmd:0x%04x, datalen:%d",
            cmd->cmd, get_unaligned_le32(cmd->addr),
            get_unaligned_le16(cmd->tcs), cmd->data_len);

    if (ret) {
        cts_err("Copy command header from user buffer failed %d", ret);
        return -EIO;
    } else
        ret = CTS_TOOL_CMD_HEADER_LENGTH;

    if (cmd->data_len > PAGE_SIZE) {
        cts_err("Write with invalid count %d", cmd->data_len);
        return -EIO;
    }

	if (count > CTS_TOOL_CMD_HEADER_LENGTH) {
    	ret = copy_from_user(cmd->data, buffer + CTS_TOOL_CMD_HEADER_LENGTH,
            count - CTS_TOOL_CMD_HEADER_LENGTH);
        if (ret) {
            cts_err("Copy command payload from user buffer len %u failed %d",
                 cmd->data_len, ret);
            return -EIO;
        }
	}

    if (cmd->cmd & BIT(0)) {
        if (cmd->data_len) {
            ret = copy_from_user(cmd->data, buffer + CTS_TOOL_CMD_HEADER_LENGTH,
                    cmd->data_len);
            if (ret) {
                cts_err("Copy command payload from user buffer len %u failed %d",
                        cmd->data_len, ret);
                return -EIO;
            }
        }
    } else {
        /* Prepare for read command */
        cts_dbg("Write read command(%d) header, prepare read size: %d",
                cmd->cmd, cmd->data_len);
        return CTS_TOOL_CMD_HEADER_LENGTH + cmd->data_len;
    }

    cts_dev = &cts_data->cts_dev;
    cts_lock_device(cts_dev);

    switch (cmd->cmd) {
    case CTS_TOOL_CMD_TCS_WRITE_HW_CMD:
        cts_tcs_write_hw_reg(cts_dev, get_unaligned_le32(cmd->addr),
                cmd->data, cmd->data_len);
        break;
    case CTS_TOOL_CMD_TCS_WRITE_DDI_CMD:
        cts_tcs_write_ddi_reg(cts_dev, get_unaligned_le32(cmd->addr),
                cmd->data, cmd->data_len);
        break;
    case CTS_TOOL_CMD_TCS_WRITE_FW_CMD:
        cts_tcs_write_reg(cts_dev, get_unaligned_le16(cmd->tcs),
                cmd->data, cmd->data_len);
        break;
    case CTS_TOOL_CMD_TCS_ENABLE_GET_RAWDATA_CMD:
    case CTS_TOOL_CMD_TCS_DISABLE_GET_RAWDATA_CMD:
        cts_info("Do nothing");
        break;
    case CTS_TOOL_CMD_SET_INT_DATA_TYPE_AND_METHOD:
        cts_set_int_data_types(cts_dev, cmd->data[0] | (cmd->data[1] << 8));
        cts_set_int_data_method(cts_dev, cmd->data[2]);
        break;
    case CTS_TOOL_CMD_UPDATE_PANEL_PARAM_IN_SRAM:
        break;
    case CTS_TOOL_CMD_DOWNLOAD_FIRMWARE_WITH_FILENAME:
        cts_info("Write firmware path: '%.*s'", cmd->data_len, cmd->data);

        memcpy(cts_tool_firmware_filepath, cmd->data, cmd->data_len);
        cts_tool_firmware_filepath[cmd->data_len] = '\0';
        break;

    case CTS_TOOL_CMD_DOWNLOAD_FIRMWARE:
        cts_info("Start download firmware path: '%s'", cts_tool_firmware_filepath);

        ret = cts_stop_device(cts_dev);
        if (ret) {
            cts_err("Stop device failed %d", ret);
            break;
        }

        ret = cts_update_firmware_from_file(cts_dev,
                cts_tool_firmware_filepath);
        if (ret)
            cts_err("Updata firmware failed %d", ret);

        ret = cts_start_device(cts_dev);
        if (ret) {
            cts_err("Start device failed %d", ret);
            break;
        }
        break;

    case CTS_TOOL_CMD_WRITE_HOSTCOMM:
        cts_dbg("Write firmware reg addr: 0x%04x val=0x%02x",
            get_unaligned_le16(cmd->addr), cmd->data[0]);

        ret = cts_fw_reg_writeb(cts_dev, get_unaligned_le16(cmd->addr),
                cmd->data[0]);
        if (ret) {
            cts_err("Write firmware reg addr: 0x%04x val=0x%02x failed %d",
                    get_unaligned_le16(cmd->addr), cmd->data[0], ret);
        }
        break;

    case CTS_TOOL_CMD_WRITE_HOSTCOMM_MULTIBYTE:
        cts_dbg("Write firmare reg addr: 0x%04x len %u",
            get_unaligned_le16(cmd->addr), cmd->data_len);
        ret = cts_fw_reg_writesb(cts_dev, get_unaligned_le16(cmd->addr),
                cmd->data, cmd->data_len);
        if (ret) {
            cts_err("Write firmare reg addr: 0x%04x len %u failed %d",
                    get_unaligned_le16(cmd->addr), cmd->data_len, ret);
        }
        break;

    case CTS_TOOL_CMD_WRITE_PROGRAM_MODE_MULTIBYTE:
        cts_dbg("Write to addr 0x%06x size %u under program mode",
            (cmd->flag << 16) | (cmd->addr[1] << 8) | cmd->addr[0],
            cmd->data_len);
        ret = cts_enter_program_mode(cts_dev);
        if (ret) {
            cts_err("Enter program mode failed %d", ret);
            break;
        }

        ret = cts_sram_writesb(cts_dev, (cmd->flag << 16) | (cmd->addr[1] << 8) |
                cmd->addr[0], cmd->data, cmd->data_len);
        if (ret)
            cts_err("Write program mode multibyte failed %d", ret);

        ret = cts_enter_normal_mode(cts_dev);
        if (ret) {
            cts_err("Enter normal mode failed %d", ret);
            break;
        }

        break;

#ifdef CONFIG_CTS_I2C_HOST
    case CTS_TOOL_CMD_I2C_DIRECT_WRITE:
        {
            u32 addr_width;
            size_t left_payload_size;    /* Payload exclude address field */
            size_t max_xfer_size;
            char *payload;

            if (cmd->addr[0] != CTS_DEV_PROGRAM_MODE_I2CADDR) {
                cmd->addr[0] = CTS_DEV_NORMAL_MODE_I2CADDR;
                addr_width = 2;
                cts_tool_direct_access_addr = get_unaligned_be16(cmd->data);
            } else {
                addr_width = cts_dev->hwdata->program_addr_width;
                cts_tool_direct_access_addr = get_unaligned_be24(cmd->data);
            }

            if (cmd->data_len < addr_width) {
                cts_err("Direct write too short %d < address width %d",
                        cmd->data_len, addr_width);
                ret = -EINVAL;
                break;
            }

            cts_dbg("Direct write to i2c_addr 0x%02x addr 0x%06x size %u",
                    cmd->addr[0], cts_tool_direct_access_addr, cmd->data_len);

            left_payload_size = cmd->data_len - addr_width;
            max_xfer_size = cts_plat_get_max_i2c_xfer_size(cts_dev->pdata);
            payload = cmd->data + addr_width;
            do {
                size_t xfer_payload_size = min(left_payload_size,
                        max_xfer_size - addr_width);
                size_t xfer_len = xfer_payload_size + addr_width;

                ret = cts_plat_i2c_write(cts_data->pdata,
                        cmd->addr[0], payload - addr_width, xfer_len, 1, 0);
                if (ret) {
                    cts_err("Direct write i2c_addr 0x%02x addr 0x%06x len %zu failed %d",
                            cmd->addr[0], cts_tool_direct_access_addr,
                            xfer_len, ret);
                    break;
                }

                left_payload_size -= xfer_payload_size;
                if (left_payload_size) {
                    payload += xfer_payload_size;
                    cts_tool_direct_access_addr += xfer_payload_size;
                    if (addr_width == 2) {
                        put_unaligned_be16(cts_tool_direct_access_addr,
                                payload - addr_width);
                    } else if (addr_width == 3) {
                        put_unaligned_be24(cts_tool_direct_access_addr,
                                payload - addr_width);
                    }
                }
            } while (left_payload_size);
        }
        break;
#endif
    default:
        cts_warn("Write unknown command %u", cmd->cmd);
        ret = -EINVAL;
        break;
    }

    cts_unlock_device(cts_dev);

    return ret ? 0 : cmd->data_len + CTS_TOOL_CMD_HEADER_LENGTH;
}

static long cts_ioctl_tcs_rw(struct cts_device *cts_dev,
        struct file *file, unsigned int cmd, unsigned long arg)
{
    int ret;
    struct cts_ioctl_tcs_rw_data rwdata;
    u8 *txbuf = NULL;
    u8 *rxbuf = NULL;

    if (copy_from_user(&rwdata,
            (struct cts_ioctl_tcs_rw_data __user *)arg,
            sizeof(rwdata))) {
        cts_err("Copy ioctl tcs rw arg to kernel failed");
        return -EFAULT;
    }

    if ((rwdata.opflags != CTS_TOOL_IOCTL_TCS_RW_OPFLAG_READ) &&
        (rwdata.opflags != CTS_TOOL_IOCTL_TCS_RW_OPFLAG_WRITE)) {
        cts_err("ioctl tcs rw with invalid opflags %u", rwdata.opflags);
        return -EINVAL;
    }

    if (rwdata.txlen > CTS_TOOL_IOCTL_TCS_RW_BUF_MAX_SIZ) {
        cts_err("Send too long: %d", rwdata.txlen);
        return -EINVAL;
    }

    if (rwdata.rxlen > CTS_TOOL_IOCTL_TCS_RW_BUF_MAX_SIZ) {
        cts_err("Recv too long: %d", rwdata.rxlen);
        return -EINVAL;
    }

    if (!rwdata.txbuf || !rwdata.rxbuf) {
        cts_err("Txbuf of Rxbuf is NULL!");
        return -EINVAL;
    }

    txbuf = memdup_user(rwdata.txbuf, rwdata.txlen);
    if (IS_ERR(txbuf)) {
        ret = PTR_ERR(txbuf);
        cts_err("Memdup txbuf failed %d", ret);
        txbuf = NULL;
        goto free_buf;
    }

    rxbuf = memdup_user(rwdata.rxbuf, rwdata.rxlen);
    if (IS_ERR(rxbuf)) {
        ret = PTR_ERR(rxbuf);
        cts_err("Memdup rxbuf failed %d", ret);
        rxbuf = NULL;
        goto free_buf;
    }

    cts_lock_device(cts_dev);
    ret = cts_tcs_tool_xtrans(cts_dev, txbuf, rwdata.txlen, rxbuf, rwdata.rxlen);
    cts_unlock_device(cts_dev);
    if (ret < 0) {
        cts_err("Trans failed %d", ret);
        goto free_buf;
    }

    ret = copy_to_user(rwdata.rxbuf, rxbuf, rwdata.rxlen);
    if (ret < 0) {
        cts_err("Copy to user failed %d", ret);
        goto free_buf;
    }

    ret = 0;

free_buf:
    if (txbuf) {
        kfree(txbuf);
        txbuf = NULL;
    }
    if (rxbuf) {
        kfree(rxbuf);
        rxbuf = NULL;
    }

    return ret;
}

static int cts_ioctl_test(struct cts_device *cts_dev,
    u32 ntests, struct cts_test_param *tests)
{
    u32 num_nodes = 0;
    int i, ret = 0;

    cts_info("ioctl test total %u items", ntests);

    num_nodes = cts_dev->fwdata.rows * cts_dev->fwdata.cols;

    for (i = 0; i < ntests; i++) {
        bool validate_data = false;
        bool validate_data_per_node = false;
        bool validate_min = false;
        bool validate_max = false;
        bool skip_invalid_node = false;
        bool stop_test_if_validate_fail = false;
        bool dump_test_data_to_console = false;
        bool dump_test_data_to_user = false;
        bool dump_test_data_to_file = false;
        bool dump_test_data_to_file_append = false;
        bool driver_log_to_user = false;
        bool driver_log_to_file = false;
        bool driver_log_to_file_append = false;
        u32 __user *user_min_threshold = NULL;
        u32 __user *user_max_threshold = NULL;
        u32 __user *user_invalid_nodes = NULL;
        int  __user *user_test_result = NULL;
        void __user *user_test_data = NULL;
        int  __user *user_test_data_wr_size = NULL;
        const char __user *user_test_data_filepath = NULL;
        void __user *user_driver_log_buf = NULL;
        int  __user *user_driver_log_wr_size = NULL;
        const char __user *user_driver_log_filepath = NULL;
        void __user *user_priv_param = NULL;
        int test_result = 0;
        int test_data_wr_size = 0;
        int driver_log_wr_size = 0;

        cts_info("ioctl test item %d: %d(%s) flags: %08x priv param size: %d",
            i, tests[i].test_item, cts_test_item_str(tests[i].test_item),
            tests[i].flags, tests[i].priv_param_size);
        /*
         * Validate arguement
         */
        validate_data =
            !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_DATA);
        validate_data_per_node =
            !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_PER_NODE);
        validate_min =
            !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_MIN);
        validate_max =
            !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_MAX);
        skip_invalid_node =
            !!(tests[i].flags & CTS_TEST_FLAG_VALIDATE_SKIP_INVALID_NODE);
        stop_test_if_validate_fail =
            !!(tests[i].flags & CTS_TEST_FLAG_STOP_TEST_IF_VALIDATE_FAILED);
        dump_test_data_to_user =
            !!(tests[i].flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_USERSPACE);
        dump_test_data_to_file =
            !!(tests[i].flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE);
        dump_test_data_to_file_append =
            !!(tests[i].flags & CTS_TEST_FLAG_DUMP_TEST_DATA_TO_FILE_APPEND);
        driver_log_to_user =
            !!(tests[i].flags & CTS_TEST_FLAG_DRIVER_LOG_TO_USERSPACE);
        driver_log_to_file =
            !!(tests[i].flags & CTS_TEST_FLAG_DRIVER_LOG_TO_FILE);
        driver_log_to_file_append =
            !!(tests[i].flags & CTS_TEST_FLAG_DRIVER_LOG_TO_FILE_APPEND);

        if (tests[i].test_result == NULL) {
            cts_err("Result pointer = NULL");
            return -EFAULT;
        }

        if (validate_data) {
            cts_info("  - Flag: Validate data");

            if (validate_data_per_node) {
                cts_info("  - Flag: Validate data per-node");
            }
            if (validate_min) {
                cts_info("  - Flag: Validate min threshold");
            }
            if (validate_max) {
                cts_info("  - Flag: Validate max threshold");
            }
            if (stop_test_if_validate_fail) {
                cts_info("  - Flag: Stop test if validate fail");
            }

            if (validate_min && tests[i].min == NULL) {
                cts_err("Min threshold pointer = NULL");
                ret = -EINVAL;
                goto store_result;
            }
            if (validate_max && tests[i].max == NULL) {
                cts_err("Max threshold pointer = NULL");
                ret = -EINVAL;
                goto store_result;
            }
            if (skip_invalid_node) {
                cts_info("  - Flag: Skip invalid node");

                if (tests[i].num_invalid_node == 0 ||
                    tests[i].num_invalid_node >= num_nodes) {
                    cts_err("Num invalid node %u out of range[0, %u]",
                        tests[i].num_invalid_node, num_nodes);
                    ret = -EINVAL;
                    goto store_result;
                }

                if (tests[i].invalid_nodes == NULL) {
                    cts_err("Invalid nodes pointer = NULL");
                    ret = -EINVAL;
                    goto store_result;
                }
            }
        }

        if (dump_test_data_to_console) {
            cts_info("  - Flag: Dump test data to console");
        }

        if (dump_test_data_to_user) {
            cts_info("  - Flag: Dump test data to user, size: %d",
                tests[i].test_data_buf_size);

            if (tests[i].test_data_buf == NULL) {
                cts_err("Test data pointer = NULL");
                ret = -EINVAL;
                goto store_result;
            }
            if (tests[i].test_data_wr_size == NULL) {
                cts_err("Test data write size pointer = NULL");
                ret = -EINVAL;
                goto store_result;
            }
            if (tests[i].test_data_buf_size < num_nodes) {
                cts_err("Test data size %d too small < %u",
                    tests[i].test_data_buf_size, num_nodes);
                ret = -EINVAL;
                goto store_result;
            }
        }

        if (dump_test_data_to_file) {
            cts_info("  - Flag: Dump test data to file%s",
                dump_test_data_to_file_append ? "[Append]" : "");

            if (tests[i].test_data_filepath == NULL) {
                cts_err("Test data filepath = NULL");
                ret = -EINVAL;
                goto store_result;
            }
        }

        if (driver_log_to_user) {
            cts_info("  - Flag: Dump driver log to user, size: %d",
                tests[i].driver_log_buf_size);

            if (tests[i].driver_log_buf == NULL) {
                cts_err("Driver log buf pointer = NULL");
                ret = -EINVAL;
                goto store_result;
            }
            if (tests[i].driver_log_wr_size == NULL) {
                cts_err("Driver log write size pointer = NULL");
                ret = -EINVAL;
                goto store_result;
            }
            if (tests[i].driver_log_buf_size < 1024) {
                cts_err("Driver log buf size %d too small < 1024",
                    tests[i].test_data_buf_size);
                ret = -EINVAL;
                goto store_result;
            }
        }

        if (driver_log_to_file) {
            cts_info("  - Flag: Dump driver log to file %s",
                driver_log_to_file_append ? "[Append]" : "");

            if (tests[i].driver_log_filepath == NULL) {
                cts_err("Driver log filepath = NULL");
                ret = -EINVAL;
                goto store_result;
            }
        }

        /*
         * Dump input parameter from user,
         * Aallocate memory for output,
         * Replace __user pointer with kernel pointer.
         */
        user_test_result = (int __user *)tests[i].test_result;
        tests[i].test_result = &test_result;

        if (validate_data) {
            int num_threshold = validate_data_per_node ? num_nodes : 1;
            int threshold_size = num_threshold * sizeof(tests[i].min[0]);

            if (validate_min) {
                user_min_threshold = (int __user *)tests[i].min;
                tests[i].min = memdup_user(user_min_threshold, threshold_size);
                if (IS_ERR(tests[i].min)) {
                    ret = PTR_ERR(tests[i].min);
                    tests[i].min = NULL;
                    cts_err("Memdup min threshold from user failed %d", ret);
                    goto store_result;
                }
            } else {
                tests[i].min = NULL;
            }
            if (validate_max) {
                user_max_threshold = (int __user *)tests[i].max;
                tests[i].max = memdup_user(user_max_threshold, threshold_size);
                if (IS_ERR(tests[i].max)) {
                    ret = PTR_ERR(tests[i].max);
                    tests[i].max = NULL;
                    cts_err("Memdup max threshold from user failed %d", ret);
                    goto store_result;
                }
            } else {
                tests[i].max = NULL;
            }
            if (skip_invalid_node) {
                user_invalid_nodes = (u32 __user *)tests[i].invalid_nodes;
                tests[i].invalid_nodes = memdup_user(user_invalid_nodes,
                    tests[i].num_invalid_node * sizeof(tests[i].invalid_nodes[0]));
                if (IS_ERR(tests[i].invalid_nodes)) {
                    ret = PTR_ERR(tests[i].invalid_nodes);
                    tests[i].invalid_nodes = NULL;
                    cts_err("Memdup invalid node from user failed %d", ret);
                    goto store_result;
                }
            }
        }

        if (dump_test_data_to_user) {
            user_test_data = (void __user *)tests[i].test_data_buf;
            tests[i].test_data_buf = vmalloc(tests[i].test_data_buf_size);
            if (tests[i].test_data_buf == NULL) {
                ret = -ENOMEM;
                cts_err("Alloc test data mem failed");
                goto store_result;
            }
            user_test_data_wr_size = (int __user *)tests[i].test_data_wr_size;
            tests[i].test_data_wr_size = &test_data_wr_size;
        }

        if (dump_test_data_to_file) {
#ifdef CFG_CTS_FOR_GKI
            cts_info("%s(): strndup_user is forbiddon with GKI Version!", __func__);
#else
            user_test_data_filepath = (const char __user *)tests[i].test_data_filepath;
            tests[i].test_data_filepath = strndup_user(user_test_data_filepath, PATH_MAX);
            if (tests[i].test_data_filepath == NULL) {
                cts_err("Strdup test data filepath failed");
                goto store_result;
            }
#endif
        }

        if (driver_log_to_user) {
            user_driver_log_buf = (void __user *)tests[i].driver_log_buf;
            tests[i].driver_log_buf = kmalloc(tests[i].driver_log_buf_size, GFP_KERNEL);
            if (tests[i].driver_log_buf == NULL) {
                ret = -ENOMEM;
                cts_err("Alloc driver log mem failed");
                goto store_result;
            }
            user_driver_log_wr_size = (int __user *)tests[i].driver_log_wr_size;
            tests[i].driver_log_wr_size = &driver_log_wr_size;
        }

        if (driver_log_to_file) {
#ifdef CFG_CTS_FOR_GKI
            cts_info("%s(): strndup_user is forbiddon with GKI Version!", __func__);
#else
            user_driver_log_filepath = (const char __user *)tests[i].driver_log_filepath;
            tests[i].driver_log_filepath = strndup_user(user_driver_log_filepath, PATH_MAX);
            if (tests[i].driver_log_filepath == NULL) {
                cts_err("Strdup driver log filepath failed");
                goto store_result;
            }
#endif
            cts_info("Log driver log to file '%s'", tests[i].driver_log_filepath);
        }

        if (driver_log_to_file || driver_log_to_user) {
            ret = cts_start_driver_log_redirect(
                tests[i].driver_log_filepath, driver_log_to_file_append,
                tests[i].driver_log_buf, tests[i].driver_log_buf_size,
                tests[i].driver_log_level);
            if (ret) {
                cts_err("Start driver log redirect failed %d", ret);
                goto store_result;
            }
        }

        if (tests[i].priv_param_size && tests[i].priv_param) {
            user_priv_param = (void __user *)tests[i].priv_param;
            tests[i].priv_param = memdup_user(user_priv_param, tests[i].priv_param_size);
            if (IS_ERR(tests[i].priv_param)) {
                ret = PTR_ERR(tests[i].priv_param);
                tests[i].priv_param = NULL;
                cts_err("Memdup priv param from user failed %d", ret);
                goto store_result;
            }
        }

        switch (tests[i].test_item) {
        case CTS_TEST_RESET_PIN:
            ret = cts_test_reset_pin(cts_dev, &tests[i]);
            break;
        case CTS_TEST_INT_PIN:
            ret = cts_test_int_pin(cts_dev, &tests[i]);
            break;
        case CTS_TEST_RAWDATA:
            ret = cts_test_rawdata(cts_dev, &tests[i]);
            break;
        case CTS_TEST_NOISE:
            ret = cts_test_noise(cts_dev, &tests[i]);
            break;
        case CTS_TEST_OPEN:
            ret = cts_test_open(cts_dev, &tests[i]);
            break;
        case CTS_TEST_SHORT:
            ret = cts_test_short(cts_dev, &tests[i]);
            break;
        case CTS_TEST_COMPENSATE_CAP:
            ret = cts_test_compensate_cap(cts_dev, &tests[i]);
            break;
        case CTS_TEST_STYLUS_RAWDATA:
            ret = cts_test_stylus_rawdata(cts_dev, &tests[i]);
            break;
        case CTS_TEST_STYLUS_NOISE:
            ret = cts_test_stylus_noise(cts_dev, &tests[i]);
            break;
        default:
            ret = ENOTSUPP;
            cts_err("Un-supported test item");
            break;
        }

/*
 * Copy result and test data back to userspace.
 */
store_result:
        if (dump_test_data_to_user) {
            if (user_test_data != NULL && test_data_wr_size > 0) {
                cts_info("Copy test data to user, size: %d", test_data_wr_size);
                if (copy_to_user(user_test_data, tests[i].test_data_buf,
                    test_data_wr_size)) {
                    cts_err("Copy test data to user failed");
                    test_data_wr_size = 0;
                    // Skip this error
                }
            }

            if (user_test_data_wr_size != NULL) {
                put_user(test_data_wr_size, user_test_data_wr_size);
            }
        }

        if (driver_log_to_user) {
            driver_log_wr_size = cts_get_driver_log_redirect_size();
            if (user_driver_log_buf != NULL && driver_log_wr_size > 0) {
                cts_info("Copy driver log to user, size: %d", driver_log_wr_size);
                if (copy_to_user(user_driver_log_buf, tests[i].driver_log_buf,
                        driver_log_wr_size)) {
                    cts_err("Copy driver log to user failed");
                    driver_log_wr_size = 0;
                    // Skip this error
                }
            }

            if (user_driver_log_wr_size != NULL) {
                put_user(driver_log_wr_size, user_driver_log_wr_size);
            }
        }

        if (user_test_result != NULL) {
            put_user(ret, user_test_result);
        } else if (tests[i].test_result != NULL) {
            put_user(ret, tests[i].test_result);
        }

        /*
         * Free memory
         */
        if (dump_test_data_to_user) {
            if (user_test_data != NULL && tests[i].test_data_buf != NULL) {
                vfree(tests[i].test_data_buf);
            }
        }
        if (validate_data) {
            if (validate_min && user_min_threshold != NULL && tests[i].min != NULL) {
                kfree(tests[i].min);
            }
            if (validate_max && user_max_threshold != NULL && tests[i].max != NULL) {
                kfree(tests[i].max);
            }
            if (skip_invalid_node) {
                if (user_invalid_nodes != NULL && tests[i].invalid_nodes != NULL) {
                    kfree(tests[i].invalid_nodes);
                }
            }
        }

        if (dump_test_data_to_file &&
            user_test_data_filepath != NULL &&
            tests[i].test_data_filepath != NULL) {
            kfree(tests[i].test_data_filepath);
        }

        if (driver_log_to_user) {
            if (user_driver_log_buf != NULL &&
                tests[i].driver_log_buf != NULL) {
                kfree(tests[i].driver_log_buf);
            }
        }

        if (driver_log_to_file) {
            if (user_driver_log_filepath != NULL &&
                tests[i].driver_log_filepath != NULL) {
                kfree(tests[i].driver_log_filepath);
            }
        }

        if (driver_log_to_file || driver_log_to_user) {
            cts_stop_driver_log_redirect();
        }

        if (user_priv_param && tests[i].priv_param) {
            kfree(tests[i].priv_param);
        }

        if (ret && stop_test_if_validate_fail) {
            break;
        }
    }

    kfree(tests);

    return ret;
}


static long cts_tool_ioctl(struct file *file, unsigned int cmd,
        unsigned long arg)
{
    struct chipone_ts_data *cts_data;
    struct cts_device *cts_dev;
    unsigned int modid;
    int ret;

    cts_info("ioctl, cmd=0x%016x, arg=0x%016lx", cmd, arg);

    cts_data = file->private_data;
    if (cts_data == NULL) {
        cts_err("IOCTL with private data = NULL");
        return -EFAULT;
    }

    cts_dev = &cts_data->cts_dev;

    switch (cmd) {
    case CTS_TOOL_IOCTL_GET_DRIVER_VERSION:
        return put_user(CTS_DRIVER_VERSION_CODE,
                (unsigned int __user *)arg);
    case CTS_TOOL_IOCTL_GET_DEVICE_TYPE:
        return put_user(cts_dev->hwdata->hwid,
                (unsigned int __user *)arg);
    case CTS_TOOL_IOCTL_GET_FW_VERSION:
        return put_user(cts_dev->fwdata.version,
                (unsigned short __user *)arg);
    case CTS_TOOL_IOCTL_GET_ROW_COL:{
        u16 rows_cols = (cts_dev->fwdata.rows << 8) | cts_dev->fwdata.cols;
        return put_user(rows_cols,
                (unsigned short __user *)arg);
    }
    case CTS_TOOL_IOCTL_GET_MODULE_ID:
        ret = cts_tcs_get_module_id(cts_dev, &modid);
        if (ret) {
            cts_err("Get module Id failed");
                return -EFAULT;
        }
        return put_user(modid,
                (unsigned int __user *)arg);

    case CTS_TOOL_IOCTL_TEST:{
        struct cts_test_ioctl_data test_arg;
        struct cts_test_param *tests_pa;

        if (copy_from_user(&test_arg, (struct cts_test_ioctl_data __user *)arg,
            sizeof(test_arg))) {
            cts_err("Copy ioctl test arg to kernel failed");
            return -EFAULT;
        }

        if (test_arg.ntests > 8) {
            cts_err("ioctl test with too many tests %u", test_arg.ntests);
            return -EINVAL;
        }

        tests_pa = memdup_user(test_arg.tests,
            test_arg.ntests * sizeof(struct cts_test_param));
        if (IS_ERR(tests_pa)) {
            int ret = PTR_ERR(tests_pa);
            cts_err("Memdump test param to kernel failed %d", ret);
            return ret;
        }

        return cts_ioctl_test(cts_dev, test_arg.ntests, tests_pa);
    }
    case CTS_TOOL_IOCTL_TCS_RW:{
        return cts_ioctl_tcs_rw(cts_dev, file, cmd, arg);
    }

    default:
        break;
    }

    return -ENOTSUPP;
}

#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 6, 0)
static struct file_operations cts_tool_fops = {
    .owner = THIS_MODULE,
    .llseek = no_llseek,
    .open = cts_tool_open,
    .read = cts_tool_read,
    .write = cts_tool_write,
    .unlocked_ioctl = cts_tool_ioctl,
};
#else
static struct proc_ops cts_tool_fops = {
    .proc_lseek = no_llseek,
    .proc_open = cts_tool_open,
    .proc_read = cts_tool_read,
    .proc_write = cts_tool_write,
    .proc_ioctl = cts_tool_ioctl,
};
#endif

int cts_tool_init(struct chipone_ts_data *cts_data)
{
    cts_info("Init");

    cts_data->procfs_entry = proc_create_data(CFG_CTS_TOOL_PROC_FILENAME,
            0660, NULL, &cts_tool_fops, cts_data);
    if (cts_data->procfs_entry == NULL) {
        cts_err("Create proc entry failed");
        return -EFAULT;
    }

    return 0;
}

void cts_tool_deinit(struct chipone_ts_data *data)
{
    cts_info("Deinit");

    if (data->procfs_entry)
        remove_proc_entry(CFG_CTS_TOOL_PROC_FILENAME, NULL);
}
#endif /* CONFIG_CTS_LEGACY_TOOL */
